package com.loong.order.handler;

import com.loong.common.enumerate.OrderStatus;
import com.loong.common.enumerate.ResultCode;
import com.loong.common.exception.GlobalException;
import com.loong.common.executor.BatchCommonExecutor;
import com.loong.common.rabbitmq.dto.OrderBase;
import com.loong.common.rabbitmq.dto.OrderDTO;
import com.loong.common.rabbitmq.dto.OrderDelivery;
import com.loong.common.rabbitmq.dto.OrderItems;
import com.loong.common.rabbitmq.dto.base.CommonEventDto;
import com.loong.common.redis.RedisLock;
import com.loong.common.redis.RedisTools;
import com.loong.common.rest.rpc.RPCResponse;
import com.loong.common.utils.TransformUtils;
import com.loong.data.dto.ShopDTOResult;
import com.loong.data.service.IShopService;
import com.loong.global.constant.RedisKey;
import com.loong.member.dto.MemberAddressDTOResult;
import com.loong.member.service.ICartService;
import com.loong.member.service.IMemberAddressService;
import com.loong.order.common.forehead.param.*;
import com.loong.order.common.forehead.result.*;
import com.loong.order.model.OrderItem;
import com.loong.order.model.PaymentOrder;
import com.loong.order.mq.OrderReport;
import lombok.extern.slf4j.Slf4j;

import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @Description: 订单服务
 * @Author: hzp
 * @Date: 2020-12-31 10:49
 */
@Slf4j
@Component
public class OrderHandler {
    @Resource
    private OrderBaseHandler orderBaseHandler;

    @Resource
    private OrderDeliveryHandler orderDeliveryHandler;

    @Resource
    private OrderItemHandler orderItemHandler;

    @Resource
    private RedisLock redisLock;

    @Resource
    private OrderReport orderReport;

    @Resource
    private BatchCommonExecutor batchCommonExecutor;

    @DubboReference(timeout = 3000, retries = 1)
    private IMemberAddressService iMemberAddressService;

    @DubboReference(timeout = 3000, retries = 1)
    private IShopService iShopService;

    @DubboReference(timeout = 3000, retries = 1)
    private ICartService iCartService;

    @Resource
    private PaymentOrderHandler paymentOrderHandler;


    public com.loong.order.model.OrderBase findOrderBaseById(Long id){
        return orderBaseHandler.findOrderBaseById(id);
    }

    /**
     * 保存 订单基础 订单详情 配送信息 （整个订单）
     * @param param 订单信息
     */
    @Transactional
    public OrderForeheadResult save(OrderForeheadParam param){
        //todo 如果用submit 这里不用设置  submit就设置了 直接保存就好了 现在直接入库先加上
        Long id = RedisTools.valueOperations().increment(RedisKey.PRIMARY_KEY.ORDER_ID);
        OrderBaseForeheadParam orderBaseForeheadParam = param.getOrderBaseForeheadParam();
        orderBaseForeheadParam.setId(id);
        orderBaseForeheadParam.setOrderNumber(id.toString());
        List<OrderItem> itemList = orderItemHandler.save(id, param.getOrderItemList());
        BigDecimal reduce = itemList.stream().map(OrderItem::getTotalPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
        log.debug("sku总价：{}",reduce);
        orderBaseForeheadParam.setTotalPrice(reduce);
        BigDecimal calculation = calculation(orderBaseForeheadParam.getPostFee(), orderBaseForeheadParam.getFoodBoxFee(), orderBaseForeheadParam.getDiscountPrice(), orderBaseForeheadParam.getTotalPrice());
        //实付
        orderBaseForeheadParam.setPayment(calculation);
        com.loong.order.model.OrderBase dbModel = orderBaseHandler.save(orderBaseForeheadParam);
        OrderDeliveryForeheadParam orderDeliveryForeheadParam =param.getOrderDeliveryForeheadParam();
        orderDeliveryForeheadParam.setOrderId(id);
        orderDeliveryForeheadParam.setOrderNumber(id.toString());
        Long addressId = orderDeliveryForeheadParam.getAddressId();
        RPCResponse<MemberAddressDTOResult> memberAddress = iMemberAddressService.findMemberAddressById(addressId);
        MemberAddressDTOResult data = memberAddress.getData();
        orderDeliveryForeheadParam = buildParam(orderDeliveryForeheadParam, data);
        orderDeliveryHandler.save(orderDeliveryForeheadParam);
        //删除下单的购物车
        log.debug("购物车id：{}",param.getCartId());
        iCartService.updatePaidCartStatus(param.getCartId());
        log.debug("RPC 成功============");
        PaymentOrder paymentOrder = PaymentOrder.builder()
                .orderId(orderBaseForeheadParam.getId())
                .orderNum(orderBaseForeheadParam.getOrderNumber())
                .payment(orderBaseForeheadParam.getPayment())
                .paymentType(orderBaseForeheadParam.getPaymentType())
                .build();
        //支付订单保存
        paymentOrderHandler.save(paymentOrder);
        return OrderForeheadResult.builder()
                .orderId(dbModel.getId())
                .build();
    }


    private OrderDeliveryForeheadParam buildParam(OrderDeliveryForeheadParam orderDeliveryForeheadParam,MemberAddressDTOResult data){
       log.debug("===============orderDeliveryForeheadParam :{}",orderDeliveryForeheadParam);
       log.debug("===========data :{}",data);
        orderDeliveryForeheadParam.setAddress(data.getAddressName());
        orderDeliveryForeheadParam.setAddressDetail(data.getAddressDetail());
        orderDeliveryForeheadParam.setAreaCode(data.getAreaCode());
        orderDeliveryForeheadParam.setGender(data.getGender());
        orderDeliveryForeheadParam.setLatitude(data.getLatitude());
        orderDeliveryForeheadParam.setLongitude(data.getLongitude());
        orderDeliveryForeheadParam.setReceiverName(data.getUsername());
        orderDeliveryForeheadParam.setReceiverMobile(data.getPhoneNum());
        return orderDeliveryForeheadParam;
    }

    /**
     * 更新订单信息
     * @param param 上报订单 base信息
     */
    public void update(OrderForeheadParam param){
        orderBaseHandler.update(param.getOrderBaseForeheadParam());
        orderDeliveryHandler.update(param.getOrderDeliveryForeheadParam());
    }

    public void deleteIds(List<Long> orderBaseIdList,List<Long> orderDeliveryIdList,List<Long> orderItemIdList){
        orderBaseHandler.deleteIds(orderBaseIdList);
        orderDeliveryHandler.deleteIds(orderDeliveryIdList);
        orderItemHandler.deleteIds(orderItemIdList);
    }

    /**
     *  批量保存订单
     * @param dataList 订单集合
     */
    @Transactional
    public void batchSave(List<OrderDTO> dataList){
        batchCommonExecutor.batchExecute(dataList, orderDTO->{
            OrderForeheadParam param = parseParam(orderDTO);
            this.save(param);
        });

    }

    /**
     * 批量更新 订单 只能更新 status 备注  备注只能修改一次 和配送信息
     * @param dataList 订单集合
     */
    @Transactional
    public void batchUpdate(List<OrderDTO> dataList){
        batchCommonExecutor.batchExecute(dataList, orderDTO->{
            OrderForeheadParam param = parseParam(orderDTO);
            this.update(param);
        });

    }
    /**
     * 批量删除 订单
     * @param dataList 订单集合
     */
    public void batchDelete(List<OrderDTO> dataList){
       List<Long> orderBaseIdList = new ArrayList();
       List<Long> orderDeliveryIdList = new ArrayList();
       List<Long> orderItemIdList = new ArrayList();
        dataList.forEach(orderDTO -> {
            orderBaseIdList.add(orderDTO.getOrderBase().getId());
            orderDeliveryIdList.add(orderDTO.getOrderDelivery().getId());
            orderDTO.getOrderItemList().forEach(orderItem -> orderItemIdList.add(orderItem.getId()));
        });
       this.deleteIds(orderBaseIdList,orderDeliveryIdList,orderItemIdList);
    }

    private OrderForeheadParam parseParam(OrderDTO orderDTO){
        return OrderForeheadParam.builder()
                .orderBaseForeheadParam(TransformUtils.transform(orderDTO.getOrderBase(), OrderBaseForeheadParam.class))
                .orderDeliveryForeheadParam(TransformUtils.transform(orderDTO.getOrderDelivery(), OrderDeliveryForeheadParam.class))
                .orderItemList(TransformUtils.transformList(orderDTO.getOrderItemList(), OrderItemForeheadParam.class))
                .build();
    }

    /**
     * 提交订单
     * @param param 订单参数
     */
    public void submitOrder(OrderForeheadParam param) {
        if(!orderItemHandler.checkPrice(param.getOrderItemList())){
            throw new GlobalException(ResultCode.SKU_PRICE_UPDATE);
        }

        //查库存
        if(checkSubtractStock(param.getOrderBaseForeheadParam().getShopId(), param.getOrderItemList())){
            log.debug("提交订单 减库存成功");
            //生成订单id
            Long id = RedisTools.valueOperations().increment(RedisKey.PRIMARY_KEY.ORDER_ID);
            OrderBaseForeheadParam orderBaseForeheadParam = param.getOrderBaseForeheadParam();
            orderBaseForeheadParam.setId(id);
            orderBaseForeheadParam.setStatus(OrderStatus.WAITING_PAYMENT.getCode());
            //订单号
            orderBaseForeheadParam.setOrderNumber(id.toString());
            OrderDeliveryForeheadParam orderDeliveryForeheadParam = param.getOrderDeliveryForeheadParam();
            orderDeliveryForeheadParam.setOrderId(id);
            orderDeliveryForeheadParam.setId(id);
            List<OrderItemForeheadParam> orderItemList = param.getOrderItemList();
            orderItemList = orderItemHandler.buildData(orderBaseForeheadParam.getId(), orderItemList);
            RPCResponse<MemberAddressDTOResult> memberAddress = iMemberAddressService.findMemberAddressById(orderDeliveryForeheadParam.getAddressId());
            MemberAddressDTOResult data = memberAddress.getData();
            orderDeliveryForeheadParam = buildParam(orderDeliveryForeheadParam, data);
            OrderReportParam reportParam =
                    OrderReportParam.builder()
                    .orderBase(TransformUtils.transform(orderBaseForeheadParam, OrderBase.class))
                    .orderDelivery(TransformUtils.transform(orderDeliveryForeheadParam, OrderDelivery.class))
                    .orderItemList(TransformUtils.transformList(orderItemList, OrderItems.class))
                    .build();

            CommonEventDto<OrderReportParam> orderReportEventDto = CommonEventDto.buildWithCreate(reportParam);
            //上报mq
            log.debug("上报数据：{}",orderReportEventDto.getData());
            orderReport.report(orderReportEventDto);
        }else{
            throw new GlobalException(ResultCode.NO_STOCK);
        }

    }

    /**
     *
     * @param orderItemList redis 检查库存 减库存操作 这里要保证 检查的时候别的人不能操作库存
     * @param shopId 店铺id
     * @return 是否减库存成功
     */
    public boolean checkSubtractStock(Long shopId,List<OrderItemForeheadParam> orderItemList){
        //记录 操作成功的 原库存记录 用于减库存失败回滚用 这里枷锁一次 避免锁竞争激烈
        Map<String,Integer> skuStockMap = new ConcurrentHashMap<>();
        for (OrderItemForeheadParam orderItem:orderItemList) {
            String key = RedisKey.ORDER.STOCK + shopId + "_" + orderItem.getGoodsSkuId();
            String LockKey =RedisKey.ORDER.LOCK_KEY+key;
                //抢锁 todo 没抢到怎么办
                if (redisLock.lock(LockKey, 20, 10, 10)) {
                    log.debug("抢到锁：{}",LockKey);
                    //获取redis 库存
                    int stockNum =  RedisTools.valueOperations().get(key,Integer.class);
                    if(stockNum>=orderItem.getNum()){
                        RedisTools.valueOperations().increment(key,-orderItem.getNum());
                        skuStockMap.put(key,orderItem.getNum());
                        //减完 释放锁
                        while (!redisLock.releaseLock(LockKey)) {
                            slows();
                        }
                    }else {
                        while (!redisLock.releaseLock(LockKey)) {
                            slows();
                        }
                        //库存不足 回滚  这种方式会导致 原本没库存的东西突然变得有库存了  如果要避免 只能串行化执行  LOCK
                        if(skuStockMap.size()>0){
                            skuStockMap.forEach((k,v)->{
                                if (redisLock.lock(RedisKey.ORDER.LOCK_KEY+k, 20, 10, 10))
                                RedisTools.valueOperations().increment(key,v);
                                //减完 释放锁
                                while (!redisLock.releaseLock(RedisKey.ORDER.LOCK_KEY+k)) {
                                    slows();
                                }
                            });
                        }
                        return false;
                    }
                }else {
                    log.debug("抢锁结果 ：{}",redisLock.lock(LockKey, 20, 10, 10));
                    //这里就代表根本没有抢到锁 结束
                    //抛异常？
                    return false;
                }

        }
        return true;
    }
    private void slows(){
        try {
            Thread.sleep(5_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public OrderDetailForeheadResult findOrderDetail(Long id) {
        OrderBaseResult orderBaseResult = orderBaseHandler.findById(id);
        List<OrderItemResult> orderItemResults = orderItemHandler.findListByOrderId(id);
        BigDecimal reduce = orderItemResults.stream().map(OrderItemResult::getTotalPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
        orderBaseResult.setTotalPrice(reduce);
        OrderDeliveryResult orderDeliveryResult = orderDeliveryHandler.findByOrderId(id);
        RPCResponse<ShopDTOResult> esShopModelResult= iShopService.findShopModelById(orderBaseResult.getShopId());
        return OrderDetailForeheadResult.builder()
                .shopDTOResult(esShopModelResult.getData())
                .orderBase(orderBaseResult)
                .orderDelivery(orderDeliveryResult)
                .orderItemList(orderItemResults)
                .build();
    }

    /**
     *
     * @param post_fee 配送费
     * @param food_box_fee 包装费
     * @param discount_price 优惠金额
     * @param total_price sku总价
     * @return
     */
    private BigDecimal calculation(BigDecimal post_fee,BigDecimal food_box_fee ,BigDecimal discount_price , BigDecimal total_price){
        BigDecimal bigDecimal = new BigDecimal(0);

        return total_price.subtract(discount_price==null? bigDecimal:discount_price).add(food_box_fee==null?bigDecimal:food_box_fee).add(post_fee==null?bigDecimal:post_fee);
    }


}
